﻿// -----------------------------------------------------------------------
// <copyright file="AddressList.cs" company="Microsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace Zen.BlackBerry.Pap
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Web;
	using System.Xml;
	using System.Xml.Schema;
	using System.Xml.Serialization;

	/// <summary>
	/// Represents a list of addresses (e.g. PINs, email addresses, etc.).
	/// </summary>
	public class AddressList : HashSet<string>, IXmlSerializable
	{
		/// <summary>
		/// Special address used to push to all addresses associated with the
		/// push application.
		/// </summary>
		public static readonly string PushAll = "push_all";

		/// <summary>
		/// BES PIN prefix
		/// </summary>
		public static readonly string BesAddressPrefix = "WAPPUSH=";

		/// <summary>
		/// BES PIN suffix
		/// </summary>
		public static readonly string BesAddressSuffix = "/TYPE=USER@rim.net";

		/// <summary>
		/// Initializes a new instance of the <see cref="AddressList"/> class.
		/// </summary>
		public AddressList()
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AddressList"/> class.
		/// </summary>
		/// <param name="address">The address.</param>
		public AddressList(string address)
		{
			Add(address);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AddressList"/> class.
		/// </summary>
		/// <param name="shouldPushAll">if set to <c>true</c> [should push all].</param>
		public AddressList(bool shouldPushAll)
		{
			Add(PushAll);
		}

		/// <summary>
		/// Gets or sets a value indicating whether this instance is using public push.
		/// </summary>
		/// <value>
		/// <c>true</c> if this instance is using public push; otherwise, <c>false</c>.
		/// </value>
		public bool IsUsingPublicPush
		{
			get;
			set;
		}

		/// <summary>
		/// Determines whether the specified address is the push-all address.
		/// </summary>
		/// <param name="address">The address.</param>
		/// <returns>
		/// <c>true</c> if the specified address is the push-all address;
		/// otherwise, <c>false</c>.
		/// </returns>
		public static bool IsPushAll(string address)
		{
			return string.Equals(address, PushAll, StringComparison.OrdinalIgnoreCase);
		}

		/// <summary>
		/// Formats the BES address.
		/// </summary>
		/// <param name="address">The address.</param>
		/// <returns></returns>
		public static string FormatBESAddress(string address)
		{
			address = HttpUtility.UrlPathEncode(address);
			return string.Format("{0}{1}{2}", BesAddressPrefix, address, BesAddressSuffix);
		}

		/// <summary>
		/// Parses the BES address.
		/// </summary>
		/// <param name="address">The address.</param>
		/// <returns></returns>
		public static string ParseBESAddress(string address)
		{
			int prefixIndex = address.IndexOf(BesAddressPrefix);
			int suffixIndex = address.LastIndexOf(BesAddressSuffix);
			
			if ((prefixIndex > -1) && (suffixIndex > 0))
			{
				address = address.Substring(
					BesAddressPrefix.Length,
					suffixIndex - BesAddressPrefix.Length);
				address = HttpUtility.UrlDecode(address);
			}
			return address;
		}

		/// <summary>
		/// Determines whether the push-all address is given in the list
		/// of addresses.
		/// </summary>
		/// <param name="addresses">The addresses.</param>
		/// <returns>
		/// <c>true</c> if address array contains a single element that is
		/// the push-all address; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsPushAll(IEnumerable<string> addresses)
		{
			if (addresses != null &&
				addresses.Count() == 1 &&
				IsPushAll(addresses.First()))
			{
				return true;
			}
			return false;
		}

		/// <summary>
		/// Adds the specified addresses to the instance.
		/// </summary>
		/// <param name="addresses">The addresses.</param>
		public void AddRange(IEnumerable<string> addresses)
		{
			foreach (string address in addresses)
			{
				Add(address);
			}
		}

		/// <summary>
		/// Validates this address list instance.
		/// </summary>
		/// <param name="usingPublicPush">
		/// <c>true</c> if address list will be used for public (BIS) push,
		/// otherwise <c>false</c>.
		/// </param>
		/// <param name="allowPushAll">
		/// if set to <c>true</c> push-all address is allowed.
		/// </param>
		/// <exception cref="InvalidOperationException">
		/// Thrown if the address list is empty.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Thrown if any of the following is true;
		/// If push-all is disallowed but the address list contains push-all.
		/// Using enterprise push and push-all address is in the address list.
		/// If push-all is specified in the address list and the count of
		/// addresses is more than 1.
		/// </exception>
		public void Validate(bool usingPublicPush, bool allowPushAll)
		{
			if (Count == 0)
			{
				throw new InvalidOperationException("No addresses were specified.");
			}

			if (!allowPushAll && Contains(PushAll))
			{
				throw new ArgumentException("push_all address not allowed.");
			}

			if (!usingPublicPush && Contains(PushAll))
			{
				throw new ArgumentException("BES (enterprise) push does not allow for a push_all address.");
			}

			if (Contains(PushAll) && Count != 1)
			{
				throw new ArgumentException("push_all address must be the only address.");
			}
		}

		#region IXmlSerializable Members

		/// <summary>
		/// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
		/// </returns>
		public XmlSchema GetSchema()
		{
			return null;
		}

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized.</param>
		public void ReadXml(XmlReader reader)
		{
			if (reader.ReadToDescendant("address"))
			{
				do
				{
					string address = reader["address-value"];
					if (!IsUsingPublicPush)
					{
						address = ParseBESAddress(address);
					}
					Add(address);
				}
				while (reader.ReadToNextSibling("address"));
			}
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized.</param>
		public void WriteXml(XmlWriter writer)
		{
			foreach(string address in this)
			{
				writer.WriteStartElement("address");

				string formattedAddress = address;
				if (!IsUsingPublicPush)
				{
					formattedAddress = FormatBESAddress(address);
				}

				writer.WriteAttributeString("address-value", formattedAddress);

				writer.WriteEndElement();
			}
		}

		#endregion
	}
}
